#include <common.h>
#include <dlfcn.h>
#include <pmemory.h>
#include <cpu.h>
#include <utils.h>

#define CONFIG_DIFFTEST
enum { DIFFTEST_TO_DUT, DIFFTEST_TO_REF };

void (*ref_difftest_memcpy)(uint64_t addr, void *buf, size_t n, bool direction) = NULL;
void (*ref_difftest_regcpy)(void *dut, bool direction) = NULL;
void (*ref_difftest_exec)(uint64_t n) = NULL;
void (*ref_difftest_raise_intr)(uint64_t NO) = NULL;
void (*ref_difftest_init)() = NULL;
extern unsigned long img_size;


#ifdef CONFIG_DIFFTEST

static bool is_skip_ref = false;

void difftest_skip_ref() {
  is_skip_ref = true;
}

void init_difftest(const char *ref_so_file, long img_size) {
  assert(ref_so_file != NULL);
  void *handle;
  handle = dlopen(ref_so_file, RTLD_LAZY);
  assert(handle);

  ref_difftest_memcpy = (void (*)(uint64_t, void *, size_t, bool))dlsym(handle, "difftest_memcpy");
  assert(ref_difftest_memcpy);

  ref_difftest_regcpy = (void (*)(void *, bool))dlsym(handle, "difftest_regcpy");
  assert(ref_difftest_regcpy);

  ref_difftest_exec = (void (*)(uint64_t))dlsym(handle, "difftest_exec");
  assert(ref_difftest_exec);

  ref_difftest_raise_intr = (void (*)(uint64_t))dlsym(handle, "difftest_raise_intr");
  assert(ref_difftest_raise_intr);

  void (*ref_difftest_init)() = (void (*)())dlsym(handle, "difftest_init");
  assert(ref_difftest_init);

  Log("Differential testing: %s", ANSI_FMT("ON", ANSI_FG_GREEN));

  ref_difftest_init();
  ref_difftest_memcpy(PMEM_BASE, guest_to_host(PMEM_BASE), img_size, DIFFTEST_TO_REF);
  ref_difftest_regcpy(&cpu, DIFFTEST_TO_REF);
}

bool difftest_checkregs(CPU_state *ref) {
  bool res = true;
  if(ref->pc != cpu.pc) {
    printf("Difftest: pc not match, ref = 0x%lx npc = 0x%lx\n", ref->pc, cpu.pc);
    res = false;
  }
  for(int i = 0; i < 32; i++) {
    if(ref->rf[i] != rf(i)){
      printf("Difftest: %s not match, ref = 0x%lx npc = 0x%lx\n", regs[i], ref->rf[i], rf(i));
      res = false;
    }
  }
  return res;
}

static void checkregs(CPU_state *ref, uint64_t pc) {
  if (!difftest_checkregs(ref)) {
    npc_state.state = NPC_ABORT;
    npc_state.npc_halt_pc = pc;
    reg_display();
  }
}

void difftest_step(uint64_t pc) {
  CPU_state ref_r;

  if (is_skip_ref) {
    // to skip the checking of an instruction, just copy the reg state to reference design
    ref_difftest_regcpy(&cpu, DIFFTEST_TO_REF);
    is_skip_ref = false;
    return;
  }

  ref_difftest_exec(1);
  ref_difftest_regcpy(&ref_r, DIFFTEST_TO_DUT);

  checkregs(&ref_r, pc);
}
  

#else
#endif